

function Template(name, file, data){
	this.name	= name ;
	this.tpl_vars	= {}; 
	this.tpl	= new Template.Smarty(Template.tpl_buffer) ;

	try{
		this.tpl.parse(data);
		if (this.tpl.error) {
			println("tpl.error:", this.tpl.error);
			return ;
		}
	}catch(e){
		println(e);
	}
}

Template.prototype.render	= function(file){
	try {
		this.tpl.process(this.tpl_vars) ;
	} catch(e) {
		println(e);
	}
}

Template.prototype.clear	= function(){
	for(var i in this.tpl_vars){
		delete this.tpl_vars[i];
	}
}

Template.Create	= function(name, file, data){
	Template.Instances [name] = new Template(name, file, data);
	return Template.Instances [name] ;
}

Template.Instances = new Object ;

Template.Smarty	= (function() {
	
	// This work is heavily inspired by the amazing efforts of Nathan Smith (ZParse).
	var header = 'with($data) {';
	var footer = '}';
	
	var _private = {
			'parseArguments': function(source, expression, self)
			{
				var i, matches,
					arguments,
					args = _private.toArray(expression, {
						expression:
						{
							'opener': '\{',
							'closer': '\}'
						}
					}).tags,
					expression = self.sanitize(expression, '()[],.<>*$@');
			
				for (i = 0; arg = args[i]; i++)
				{
					expression = expression.replace(arg, '(.*)');
					args[i] = arg.replace('\{', '').replace('\}', '');
				}
				
				if (matches = source.match(new RegExp(expression)))
				{
					for (i = 0; arg = args[i]; i++)
					{
						arguments[arg] = matches[i + 1];
					}
				}
				return arguments;
			},
			'parseTag': function(source, imp, self)
			{
				var i, delimiter,
					arguments = {},
					tag = '',
					tags = [],
					regex = '',
					closer = '';

				for (i in imp)
				{
					if (source.indexOf(imp[i].opener) === 0)
					{
						delimiter = i;
						break;
					}
				}

				if (!delimiter)
				{
					return false;
				}

				source = source.substring(imp[delimiter].opener.length, source.indexOf(imp[delimiter].closer));

				if (imp[delimiter].tags)
				{
					for (i in imp[delimiter].tags)
					{ 
						if (imp[delimiter].tags.hasOwnProperty(i))
						{
							if( i != 'for' && i != 'foreach' ) 
								tags.push(i);
						}
					}
					
					regex = '^(\\/){0,1}(' + tags.join('|') + '|foreach|for)\\s*(.*)';
					
					if (!(matches = source.match(new RegExp(regex))))
					{
						return false;
					}

					closer = matches[1] ? true : false;
					tag = matches[2];
					source = matches[3];
				}

				if (tag)
				{
					if (imp[delimiter].tags[tag].type == 'single' && closer)
					{
						return false;
					}
					if (imp[delimiter].tags[tag].type == 'block' && closer)
					{
						return {
							'delimiter': delimiter,
							'tag': tag,
							'closer': true
						};
					}
				}

				if (tag && imp[delimiter].tags[tag].arguments)
				{
					arguments = _private.parseArguments(source, imp[delimiter].tags[tag].arguments, self);
				}
				else if (!tag && imp[delimiter].arguments)
				{
					arguments = _private.parseArguments(source, imp[delimiter].arguments, self);
				}

				return {
					'delimiter': delimiter,
					'tag': tag,
					'source': source,
					'arguments': arguments
				};
			},
			'toArray': function(source, imp)
			{
				var i, opener, closer, delimiter,
					text = [], tags = [], all = [];

				while(source)
				{
					for (i in imp)
					{
						if (imp.hasOwnProperty(i))
						{
							if(!delimiter || source.indexOf(imp[delimiter].opener) == -1)
							{
								delimiter = i;
							}
							if(source.indexOf(imp[i].opener) != -1)
							{
								if(source.indexOf(imp[delimiter].opener) > source.indexOf(imp[i].opener))
								{
									delimiter = i;
								}
							}
						}
					}

					opener = source.indexOf(imp[delimiter].opener);
					closer = source.indexOf(imp[delimiter].closer) + imp[delimiter].closer.length;
					
					if (opener != -1)
					{
						text.push(source.substring(0,opener));
						tags.push(source.substring(opener,closer));
						source = source.substring(closer);
					}
					else
					{
						text.push(source);
						source = '';
					}
				}

				for (i = 0; i < text.length; i++)
				{
					all.push(text[i]);
					if(tags[i])
					{
						all.push(tags[i]);
					}
				}
				
				return {
					'text': text,
					'tags': tags,
					'all': all
				};
			},
			'toTree': function(array, imp, self)
			{
				var obj, result, current,
					doc = {
						'isDocument':true,
						'innerSource': array.join(''),
						'children': []
					},
					current = doc,
					addChild = function(parent,child)
					{
						child.number = parent.children.length;
						parent.children.push(child);
					};

				for (var i = 0; i<array.length; i++)
				{
					if (!(result = _private.parseTag(array[i], imp, self)))
					{
						if (array[i]) {
							array[i].parent = current;
							addChild(current, array[i]);
						}
					} else {
						obj = {
							'index': i,
							'tag': result.tag,
							'delimiter': result.delimiter,
							'arguments': result.arguments,
							'argSource': result.source,
							'parent': current
						};
						
						if (result.tag && imp[result.delimiter].tags[result.tag].noTextBefore && !result.closer && typeof current.children[current.children.length - 1] == 'string')
						{
							current.children.pop();
			            		}

						if(result.tag && imp[result.delimiter].tags[result.tag].type == 'block')
						{
							if(!result.closer)
							{
								addChild(current, obj);
								current = obj;
								current.children = [];
							}
							else if (current.tag == result.tag)
							{
								current.innerSource = '';
								for (var j = current.index + 1; j < i; j++)
								{
									current.innerSource += array[j];
								}
								current = current.parent;
							}
						}
						else
						{
							addChild(current, obj);
						}
					}
				}
				return doc;
			},
			'toScript': function(tree, escape, imp, self)
			{
				var result, i = 0,
					content = [];
				// tree.innerSource 
				if (typeof tree == 'string')
				{
					return '$write(\'' + self.sanitize(tree, escape) + '\');';
				}

				if (tree.children)
				{
					while (child = tree.children[i++]) content.push( _private.toScript(child, escape, imp, self));
				}

				if (!tree.isDocument)
				{
					if (tree.tag)
					{
						return imp[tree.delimiter].tags[tree.tag].handler(tree, content.join(''), self);
					}
					else
					{
						return imp[tree.delimiter].handler(tree, content.join(''), self);
					}
				}
				else
				{
					return content.join('');
			    }
			}
		} ;
	
	var Template = function(_print, options) {
		this.options	= {
			'sanitize': ['\\', '\'', '"', ['\n','\\n'], ['\t','\\t'], ['\r', '\\r']]
		} ;
		if( options instanceof Object ) for(var i in options){
			this.options[i]	= options[i] ;
		}
		this.tpl_buffer	= _print ;
	}
	
	Template.prototype.parse	= function(source) {
		var implementation = this.implementation,
			arraySource = _private.toArray(source, implementation, this),
			treeSource = _private.toTree(arraySource.all, implementation, this),
			scriptSource = _private.toScript(treeSource, this.options.sanitize, implementation, this);

		try
		{
			// println(scriptSource);
			this.executer = new Function('$data, $write', header + scriptSource + footer);
			return true;
		}
		catch(e)
		{
			this.error = e;
			return false;
		}
	};
	
	Template.prototype.process	=  function(data, bind){
		try
		{
			return this.executer.apply((bind || this), [data, this.tpl_buffer ]);
		}
		catch(e)
		{
			this.error = e;
		}
	};
	
	Template.prototype.sanitize	= function(source, list) {
		if (typeof(list) == 'string' ) {
			var _list = list ;
			list = [] ;
			for (var i = 0; i < _list.length ; i++) {
				list[i]	= _list.charAt(i) ;
			}
		}
		for (var i = 0; i < list.length ; i++) {
			var character = list[i] ;
			if (typeof(character) != 'string' )
			{
				source =  source.replace(new RegExp(character[0], 'gi'), character[1]);
			}
			else
			{
				source = source.replace(new RegExp('\\' + character, 'gi'), '\\' + character);
			}
		}
		return source;
	};
	
	Template.prototype.implementation = {
		'print':
		{
			'opener': '{=',
			'closer': '}',
			'handler': function(tree, content, self)
			{
				return '$write(' + tree.argSource + ');';
			}
		},
		'script': {
			'opener': '{:',
			'closer': '}',
			'handler': function(tree, content, self)
			{
				return tree.argSource;
			}
		},
		'statement':
		{
			'opener': '{',
			'closer': '}',
			'tags':
			{
				'if':
				{
					'type': 'block',
					'handler': function(tree, content, self)
					{
						return 'if (' + tree.argSource + ') {' + content + '}';
					}
				},
				'elseif':
				{
					'type': 'block',
					'noTextBefore': true,
					'handler': function(tree, content, self)
					{
						return 'else if (' + tree.argSource + ') {' + content + '}';
					}
				},
				'else':
				{
					'type': 'block',
					'noTextBefore': true,
					'handler': function(tree, content, self)
					{
						return 'else {' + content + '}';
					}
				},

				'macro':
				{
					'type': 'block',
					'arguments': '{name}({args})',
					'handler': function(tree, content, self)
					{
						var name = tree.arguments.name,
							args = tree.arguments.args,
							header = 'with($data) {',
							footer = '} ; return \'\'; ';
						return 'var ' + name + ' = function(' + args + ') {' + header + content + footer + '};';
					}
				},
				'cdata':
				{
					'type': 'block',
					'handler': function(tree, content, self)
					{
						return '$write(\'' + self.sanitize(tree.source, self.options.sanitize) + '\');';
					}
				},
				
				'for':
				{
					'type': 'block',
					'arguments': '{element} in {object}',
					'handler': function(tree, content, self)
					{
						var element = tree.arguments.element,
							object = tree.arguments.object,
							condition = (tree.parent.children[tree.number + 1] && tree.parent.children[tree.number + 1].tag == 'else'),
							wrapper = 'if (typeOf(' + object + ') == "Array" && ' + object + '.length > 0){';

						return (condition ? wrapper : '') +
								'if(' + object + ' instanceof Array) {' +
									'for (var ' + element + ' = 0; ' + element + ' < ' + object + '.length; ' + element,'++) {' +
										content +
									'}' +
								'} else {' +
									'for (var ' + element + ' in ' + object + ') {' +
										content +
									'}' +
								'}'+
							(condition ? '}' : '');
					}
				},
				
				'foreach':
				{
					'type': 'block',
					'arguments': '{element} in {object}',
					'handler': function(tree, content, self)
					{
						var element = tree.arguments.element,
							object = tree.arguments.object,
							condition = (tree.parent.children[tree.number + 1] && tree.parent.children[tree.number + 1].tag == 'else'),
							wrapper = 'if (' + object + ' instanceof Array && ' + object + '.length > 0) {';

						return (condition ? wrapper : '') +
								'var ' + element + ';' +
								'if (' + object + ' instanceof Array) {' +
									'for (var ' + element + '_index = 0; ' + element + ' = ' + object + '[' + element + '_index]; ' + element + '_index++) {' +
										content +
									'}' +
								'} else {' +
									'for (var ' + element + '_index in ' + object + ') {' +
										element + ' = ' + object + '[' + element + '_index];' +
										content +
									'}'+
								'}' +
							(condition ? '}' : '');
					}
				}
			}
		}
	};
	
	return Template ;
})();